 /*******************************************************************************
  * Copyright (c) 2003, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.navigator;

 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.Map ;
 import java.util.Set ;

 import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.ISafeRunnable;
 import org.eclipse.core.runtime.SafeRunner;
 import org.eclipse.jface.action.GroupMarker;
 import org.eclipse.jface.action.IContributionItem;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.action.MenuManager;
 import org.eclipse.jface.action.Separator;
 import org.eclipse.jface.viewers.ISelectionProvider;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.jface.viewers.StructuredViewer;
 import org.eclipse.ui.IActionBars;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.actions.ActionContext;
 import org.eclipse.ui.actions.ActionGroup;
 import org.eclipse.ui.internal.navigator.NavigatorContentService;
 import org.eclipse.ui.internal.navigator.NavigatorPlugin;
 import org.eclipse.ui.internal.navigator.actions.CommonActionDescriptorManager;
 import org.eclipse.ui.internal.navigator.actions.CommonActionProviderDescriptor;
 import org.eclipse.ui.internal.navigator.extensions.CommonActionExtensionSite;
 import org.eclipse.ui.internal.navigator.extensions.SkeletonActionProvider;

 /**
  * <p>
  * Provides context menu items and {@link IActionBars} contributions for a particular abstract
  * viewer. The interface matches that of {@link ActionGroup} and may be used in the same manner.
  * Clients must call
  * {@link NavigatorActionService#prepareMenuForPlatformContributions(MenuManager, ISelectionProvider, boolean)}
  * when using this class to allow object or viewer contributions. The
  * <b>org.eclipse.ui.navigator.viewer/viewer/popupMenu</b> element may override whether platform
  * contributions are allowed to the menu with its <i>allowsPlatformContributions</i> attribute.
  * "Platform Contributions" are menu items that are added through the <b>org.eclipse.ui.popupMenus</b>
  * extension point.
  * </p>
  * <p>
  * A {@link CommonActionProvider} has opportunities to contribute to the context menu and
  * {@link org.eclipse.ui.IActionBars} whenever the selection in the viewer changes. Action Providers
  * are selected based on the enablement expressions of their associated content extension or their
  * own enablement expression if it is declared as a top-level &lt;actionProvider /&gt; element (of
  * the <b>org.eclipse.ui.navigator.navigatorContent</b> extension point). See the schema
  * documentation of <b>org.eclipse.ui.navigator.navigatorContent</b> for more information on how to
  * specify an Action Provider.
  * </p>
  * <p>
  * Clients that reuse this service outside of an instance of {@link CommonNavigator} must be sure
  * that {{@link #fillActionBars(IActionBars)} is called whenever the selection changes. The
  * retargetable actions for each selection could change, based on who contributed the items.
  *
  * @since 3.2
  *
  */
 public final class NavigatorActionService extends ActionGroup implements IMementoAware {

     private static final IContributionItem[] DEFAULT_GROUPS = new IContributionItem[]{new Separator(ICommonMenuConstants.GROUP_NEW), new GroupMarker(ICommonMenuConstants.GROUP_GOTO), new GroupMarker(ICommonMenuConstants.GROUP_OPEN), new GroupMarker(ICommonMenuConstants.GROUP_OPEN_WITH), new Separator(ICommonMenuConstants.GROUP_EDIT), new GroupMarker(ICommonMenuConstants.GROUP_SHOW), new GroupMarker(ICommonMenuConstants.GROUP_REORGANIZE), new GroupMarker(ICommonMenuConstants.GROUP_PORT), new Separator(ICommonMenuConstants.GROUP_GENERATE), new Separator(ICommonMenuConstants.GROUP_SEARCH), new Separator(ICommonMenuConstants.GROUP_BUILD), new Separator(ICommonMenuConstants.GROUP_ADDITIONS), new Separator(ICommonMenuConstants.GROUP_PROPERTIES)};

     private final ICommonViewerSite commonViewerSite;

     private final StructuredViewer structuredViewer;

     private final NavigatorContentService contentService;

     private final INavigatorViewerDescriptor viewerDescriptor;

     private final Set actionProviderDescriptors = new HashSet ();

     /*
      * Map of CommonActionProviderDescriptors to CommonActionProviders
      */
     private final Map actionProviderInstances = new HashMap ();

     private IMemento memento;

     private IContributionItem[] menuGroups;

     private boolean disposed = false;

     /**
      * @param aCommonViewerSite
      * A site that provides information about the context for extensions.
      * @param aStructuredViewer
      * The associated StructuredViewer. Used to initialize extensions. <b>May NOT be
      * null.</b>
      * @param aContentService
      * The associated INavigatorContentService (for extensions that coordinate behavior
      * with content extensions -- either nested or top-level action providers). <b>May
      * NOT be null.</b>
      */
     public NavigatorActionService(ICommonViewerSite aCommonViewerSite, StructuredViewer aStructuredViewer, INavigatorContentService aContentService) {
         super();
         Assert.isNotNull(aCommonViewerSite);
         Assert.isNotNull(aStructuredViewer);
         Assert.isNotNull(aContentService);

         commonViewerSite = aCommonViewerSite;
         contentService = (NavigatorContentService) aContentService;
         structuredViewer = aStructuredViewer;
         viewerDescriptor = contentService.getViewerDescriptor();

     }

     /**
      * Prepares the menu for object contributions, if the option is set in the extension. The option
      * is controlled by the &lgt;popupMenu /&gt; element's 'allowPlatformContributions' attribute.
      * Clients may choose to ignore this setting by supplying a value of <b>true</b> for the
      * <code>force</code> attribute.
      *
      * @param menu
      * The context menu of the IViewPart
      * @param aSelectionProvider
      * The selection provider that will supplement actions with a valid, current
      * selection.
      * @param force
      * A value of 'true' forces the menu to be registered for object/view contributions.
      * Otherwise, the option from the extension point will be respected. See
      * <b>org.eclipse.ui.navigator.viewer/viewer</b> for more information.
      */
     public void prepareMenuForPlatformContributions(MenuManager menu, ISelectionProvider aSelectionProvider, boolean force) {
         Assert.isTrue(!disposed);

         if (commonViewerSite instanceof ICommonViewerWorkbenchSite) {
             /*
              * Hooks into the Eclipse framework for Object contributions, and View contributions.
              */
             if (force || viewerDescriptor.allowsPlatformContributionsToContextMenu()) {
                 ((ICommonViewerWorkbenchSite) commonViewerSite).registerContextMenu(contentService.getViewerDescriptor().getPopupMenuId(), menu, aSelectionProvider);
             }
         }
     }

     /**
      * Requests that the service invoke extensions to fill the given menu with Action Providers that
      * are interested in elements from the given selection.
      *
      * <p>
      * Object contributions (see <b>org.eclipes.ui.popupMenus</b>) may also respected by this
      * method if <code>toRespectObjectContributions</code> is true.
      * </p>
      *
      * @param aMenu
      * The menu being presented to the user.
      * @see ActionGroup#fillContextMenu(IMenuManager)
      */
     public void fillContextMenu(IMenuManager aMenu) {
         Assert.isTrue(!disposed);

         if (menuGroups == null) {
             createMenuGroups();
         }

         for (int i = 0; i < menuGroups.length; i++) {
             aMenu.add(menuGroups[i]);
         }

         addCommonActionProviderMenu(aMenu);

     }

     private void createMenuGroups() {
         MenuInsertionPoint[] customPoints = viewerDescriptor.getCustomInsertionPoints();

         if (customPoints == null) {
             menuGroups = DEFAULT_GROUPS;
         } else {
             menuGroups = new IContributionItem[customPoints.length];
             for (int i = 0; i < customPoints.length; i++) {
                 if (customPoints[i].isSeparator()) {
                     menuGroups[i] = new Separator(customPoints[i].getName());
                 } else {
                     menuGroups[i] = new GroupMarker(customPoints[i].getName());
                 }
             }
         }
     }

     /**
      * @param aMenu
      */
     private void addCommonActionProviderMenu(IMenuManager aMenu) {

         CommonActionProviderDescriptor[] providerDescriptors = CommonActionDescriptorManager.getInstance().findRelevantActionDescriptors(contentService, getContext());
         if (providerDescriptors.length > 0) {
             CommonActionProvider provider = null;
             for (int i = 0; i < providerDescriptors.length; i++) {
                 try {
                     provider = getActionProviderInstance(providerDescriptors[i]);
                     provider.setContext(getContext());
                     provider.fillContextMenu(aMenu);
                 } catch (Throwable t) {
                     NavigatorPlugin.logError(0, t.getMessage(), t);
                 }
             }
         }
     }

     /**
      * Request that the service invoke extensions to fill the given IActionBars with retargetable
      * actions or view menu contributions from Action Providers that are interested in the given
      * selection.
      *
      * @param theActionBars
      * The action bars in use by the current view site.
      * @see ActionGroup#fillActionBars(IActionBars)
      */
     public void fillActionBars(IActionBars theActionBars) {
         Assert.isTrue(!disposed);

         theActionBars.clearGlobalActionHandlers();
         ActionContext context = getContext();
         if (context == null) {
             context = new ActionContext(StructuredSelection.EMPTY);
         }

         CommonActionProviderDescriptor[] providerDescriptors = CommonActionDescriptorManager.getInstance().findRelevantActionDescriptors(contentService, context);
         if (providerDescriptors.length > 0) {
             CommonActionProvider provider = null;
             for (int i = 0; i < providerDescriptors.length; i++) {
                 try {
                     provider = getActionProviderInstance(providerDescriptors[i]);
                     if(provider != null) {
                         provider.setContext(context);
                         provider.fillActionBars(theActionBars);
                         provider.updateActionBars();
                     }

                 } catch (RuntimeException e) {
                     NavigatorPlugin.logError(0, e.getMessage(), e);
                 }
             }
         }
         theActionBars.updateActionBars();
         theActionBars.getMenuManager().update();
     }

     /**
      * Dispose of any state or resources held by the service.
      *
      * @see ActionGroup#dispose()
      */
     public void dispose() {
         synchronized (actionProviderInstances) {
             for (Iterator iter = actionProviderInstances.values().iterator(); iter.hasNext();) {
                 CommonActionProvider element = (CommonActionProvider) iter.next();
                 element.dispose();
             }
             actionProviderInstances.clear();
         }
         actionProviderDescriptors.clear();
         disposed = false;
     }

     /**
      * Use the given memento to restore the state of each Action Provider as it is initialized.
      *
      * @param aMemento
      * The memento retrieved from the dialog settings
      */
     public void restoreState(IMemento aMemento) {
         Assert.isTrue(!disposed);
         memento = aMemento;

         synchronized (actionProviderInstances) {
             for (Iterator actionProviderIterator = actionProviderInstances.values().iterator(); actionProviderIterator.hasNext();) {
                 final CommonActionProvider provider = (CommonActionProvider) actionProviderIterator.next();
                 ISafeRunnable runnable = new ISafeRunnable() {
                     public void run() throws Exception {
                         provider.restoreState(memento);
                     }

                     public void handleException(Throwable exception) {
                         NavigatorPlugin.logError(0, "Could not restore state for action provider " + provider.getClass(), exception); //$NON-NLS-1$

                     }
                 };
                 SafeRunner.run(runnable);

             }
         }
     }

     /**
      * Request that Action Providers save any state that they find interesting.
      *
      * @param aMemento
      * The memento retrieved from the dialog settings
      */
     public void saveState(IMemento aMemento) {
         Assert.isTrue(!disposed);

         memento = aMemento;
         CommonActionProvider provider = null;
         synchronized (actionProviderInstances) {
             for (Iterator actionProviderIterator = actionProviderInstances.values().iterator(); actionProviderIterator.hasNext();) {
                 provider = (CommonActionProvider) actionProviderIterator.next();
                 provider.saveState(memento);
             }
         }
     }

     private CommonActionProvider getActionProviderInstance(
             CommonActionProviderDescriptor aProviderDescriptor) {
         CommonActionProvider provider = null;
         try {
             provider = (CommonActionProvider) actionProviderInstances
                     .get(aProviderDescriptor);
             if (provider != null) {
                 return provider;
             }
             synchronized (actionProviderInstances) {
                 provider = (CommonActionProvider) actionProviderInstances
                         .get(aProviderDescriptor);
                 if (provider == null) {
                     provider = aProviderDescriptor.createActionProvider();
                     if (provider != null) {
                         initialize(aProviderDescriptor.getId(), provider);
                         actionProviderInstances.put(aProviderDescriptor, provider);
                     } else {
                         actionProviderInstances.put(aProviderDescriptor,
                                 (provider = SkeletonActionProvider.INSTANCE));
                     }
                 }
             }
         } catch(Throwable t) {
             NavigatorPlugin.logError(0, t.getMessage(), t);
         }
         return provider;
     }

     private void initialize(String id, CommonActionProvider anActionProvider) {
         if (anActionProvider != null && anActionProvider != SkeletonActionProvider.INSTANCE) {
             ICommonActionExtensionSite configuration = new CommonActionExtensionSite(id, commonViewerSite, contentService, structuredViewer);
             anActionProvider.init(configuration);
             anActionProvider.restoreState(memento);
             anActionProvider.setContext(new ActionContext(StructuredSelection.EMPTY));
             if (commonViewerSite instanceof ICommonViewerWorkbenchSite) {
                 anActionProvider.fillActionBars(((ICommonViewerWorkbenchSite) commonViewerSite).getActionBars());
             }
         }
     }
 }

